package com.md.base;

import com.fasterxml.jackson.core.type.TypeReference;
import com.google.common.collect.Lists;
import com.md.base.exception.ServerException;
import com.md.config.client.ClientBefore;
import com.md.util.ObjectUtil;
import com.md.util.StringUtil;
import com.md.value.constants.CodeConstants;
import io.vavr.control.Try;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.retry.support.RetryTemplate;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StopWatch;
import org.springframework.web.client.HttpClientErrorException;
import org.springframework.web.client.HttpStatusCodeException;
import org.springframework.web.client.RestTemplate;

/**
 * todo 有问题，应抽象出一个普通的baseClient，然后通过两个类实现该抽象类，来控制是否使用重试，而不是写死使用重试
 *
 * @author 刘骄阳
 * 2022-09-23 22:39
 */
@Slf4j
@RequiredArgsConstructor
public class BaseClient {
  private final RetryTemplate retryTemplate;
  private final RestTemplate restTemplate;
  private final ClientBefore clientBefore;

  public HttpHeaders buildHeaders() {
    HttpHeaders headers = new HttpHeaders();
    headers.setContentType(MediaType.APPLICATION_JSON);
    headers.setAccept(Lists.newArrayList(MediaType.APPLICATION_JSON));
    // 前置处理请求
    if (ObjectUtil.notEmpty(clientBefore)) {
      clientBefore.before(headers);
    }
    return headers;
  }

  /**
   * 发送请求
   *
   * @param url     url地址
   * @param request 入参
   * @param target  接受响应类型
   * @return 响应实体
   */
  public <T, R> T post(final String url,
                       final R request,
                       final Class<T> target) {
    String body = sendRequest(url, HttpMethod.POST, new HttpEntity<>(request, buildHeaders()));
    return StringUtil.jsonToObject(body, target);
  }

  /**
   * 发送请求
   *
   * @param url     url地址
   * @param request 入参
   * @param header  请求头
   * @param target  接受响应类型
   * @return 响应实体
   */
  public <T, R> T post(final String url,
                       final R request,
                       final HttpHeaders header,
                       final Class<T> target) {
    String body = sendRequest(url, HttpMethod.POST, new HttpEntity<>(request, header));
    return StringUtil.jsonToObject(body, target);
  }

  /**
   * 发送请求
   *
   * @param url     url地址
   * @param request 入参
   * @param target  接受响应类型
   * @return 响应实体
   */
  public <T, R> T post(final String url,
                       final R request,
                       final TypeReference<T> target) {
    String body = sendRequest(url, HttpMethod.POST, new HttpEntity<>(request, buildHeaders()));
    return StringUtil.jsonToObject(body, target);
  }

  /**
   * 发送请求
   *
   * @param url    url地址
   * @param target 接受响应类型
   * @return 响应实体
   */
  public <T> T get(final String url,
                   final TypeReference<T> target) {
    String body = sendRequest(url, HttpMethod.GET, new HttpEntity<>(null, buildHeaders()));
    return StringUtil.jsonToObject(body, target);
  }


  private String sendRequest(final String url, final HttpMethod httpMethod,
                             final HttpEntity<?> request) {

    return retryTemplate.execute(context -> {
      if (context.getRetryCount() > 0) {
        log.info("开始第{}次重试;", context.getRetryCount(), context.getLastThrowable());
      }
      // 重试策略
      StopWatch stopWatch = new StopWatch();
      stopWatch.start();
      String responseBody = null;
      try {
        ResponseEntity<String> exchange = restTemplate.exchange(url, httpMethod, request, String.class);
        if (ObjectUtils.isEmpty(exchange)) {
          throw new HttpClientErrorException(HttpStatus.NOT_FOUND);
        }
        HttpStatus status = exchange.getStatusCode();
        HttpHeaders headers = exchange.getHeaders();
        log.info("响应状态: {}, 响应头: {}", StringUtil.objectToJson(status), StringUtil.objectToJson(headers));
        responseBody = exchange.getBody();
      } catch (HttpStatusCodeException e) {
        log.error("服务异常: {}", e.getMessage());
        throw ServerException.build(e.getStatusCode().value(), e.getMessage());
      } catch (Exception e) {
        log.error("请求调用异常: {}", e.getMessage(), e);
        throw ServerException.build(CodeConstants.ERROR_CODE, e.getMessage());
      } finally {
        stopWatch.stop();
        log.info("请求接口: {}, 参数: {}, 响应: {}, 耗时: {}ms", url,
          Try.of(() -> {
            if (request.getBody() instanceof HttpEntity) {
              return "";
            } else {
              return StringUtil.objectToJson(request);
            }
          }), responseBody, stopWatch.getTotalTimeMillis());
      }
      return responseBody;
    });
  }
}
